package cn.iocoder.yudao.framework.mybatis.autotable.ddl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.mybatis.autotable.anotation.ColumnComment;
import cn.iocoder.yudao.framework.mybatis.autotable.anotation.ColumnDefault;
import cn.iocoder.yudao.framework.mybatis.autotable.anotation.TableComment;
import cn.iocoder.yudao.framework.mybatis.autotable.creator.JavadocExtractor;
import cn.iocoder.yudao.framework.mybatis.autotable.ddl.ColumnType;
import cn.iocoder.yudao.framework.mybatis.autotable.ddl.DDLGenerator;
import cn.iocoder.yudao.framework.mybatis.autotable.ddl.DDLGeneratorConfig;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

public class DefaultDDLGenerator implements DDLGenerator {

    // 配置参数
    private final DDLGeneratorConfig config;

    // 类型映射配置
    private static final Map<Class<?>, String> JAVA_TO_MYSQL_TYPE = new HashMap<>();
    private static final Map<Class<?>, String> JAVA_TO_MYSQL_DEFAULT = new HashMap<>();
    private static final Map<String, String> FIELD_NAME_TO_MYSQL_TYPE = new HashMap<>();

    static {
        // 初始化类型映射
        JAVA_TO_MYSQL_TYPE.put(Long.class, "bigint");
        JAVA_TO_MYSQL_TYPE.put(long.class, "bigint");
        JAVA_TO_MYSQL_TYPE.put(Integer.class, "int");
        JAVA_TO_MYSQL_TYPE.put(int.class, "int");
        JAVA_TO_MYSQL_TYPE.put(Short.class, "smallint");
        JAVA_TO_MYSQL_TYPE.put(short.class, "smallint");
        JAVA_TO_MYSQL_TYPE.put(Byte.class, "tinyint");
        JAVA_TO_MYSQL_TYPE.put(byte.class, "tinyint");
        JAVA_TO_MYSQL_TYPE.put(Boolean.class, "bit(1)");
        JAVA_TO_MYSQL_TYPE.put(boolean.class, "bit(1)");
        JAVA_TO_MYSQL_TYPE.put(String.class, "varchar(255)");
        JAVA_TO_MYSQL_TYPE.put(LocalDateTime.class, "datetime");
        JAVA_TO_MYSQL_TYPE.put(Date.class, "datetime");
        JAVA_TO_MYSQL_TYPE.put(java.sql.Date.class, "date");
        JAVA_TO_MYSQL_TYPE.put(java.sql.Timestamp.class, "timestamp");
        JAVA_TO_MYSQL_TYPE.put(Double.class, "double");
        JAVA_TO_MYSQL_TYPE.put(double.class, "double");
        JAVA_TO_MYSQL_TYPE.put(Float.class, "float");
        JAVA_TO_MYSQL_TYPE.put(float.class, "float");
        JAVA_TO_MYSQL_TYPE.put(BigDecimal.class, "decimal(18,2)");

        // 初始化默认值映射
        JAVA_TO_MYSQL_DEFAULT.put(Long.class, "0");
        JAVA_TO_MYSQL_DEFAULT.put(long.class, "0");
        JAVA_TO_MYSQL_DEFAULT.put(Integer.class, "0");
        JAVA_TO_MYSQL_DEFAULT.put(int.class, "0");
        JAVA_TO_MYSQL_DEFAULT.put(Boolean.class, "b'0'");
        JAVA_TO_MYSQL_DEFAULT.put(boolean.class, "b'0'");
        JAVA_TO_MYSQL_DEFAULT.put(String.class, "''");

        // 特殊字段类型映射
        FIELD_NAME_TO_MYSQL_TYPE.put("requestParams", "text");
        FIELD_NAME_TO_MYSQL_TYPE.put("responseBody", "text");
        FIELD_NAME_TO_MYSQL_TYPE.put("exceptionMessage", "text");
        FIELD_NAME_TO_MYSQL_TYPE.put("exceptionRootCauseMessage", "text");
        FIELD_NAME_TO_MYSQL_TYPE.put("exceptionStackTrace", "text");
        FIELD_NAME_TO_MYSQL_TYPE.put("traceId", "varchar(64)");
        FIELD_NAME_TO_MYSQL_TYPE.put("userAgent", "varchar(512)");
        FIELD_NAME_TO_MYSQL_TYPE.put("requestMethod", "varchar(16)");
        FIELD_NAME_TO_MYSQL_TYPE.put("requestUrl", "varchar(255)");
        FIELD_NAME_TO_MYSQL_TYPE.put("userIp", "varchar(50)");
        FIELD_NAME_TO_MYSQL_TYPE.put("exceptionName", "varchar(128)");
        FIELD_NAME_TO_MYSQL_TYPE.put("columnComment", "varchar(500)");
        FIELD_NAME_TO_MYSQL_TYPE.put("example", "varchar(64)");
        FIELD_NAME_TO_MYSQL_TYPE.put("dictType", "varchar(200)");
        FIELD_NAME_TO_MYSQL_TYPE.put("listOperationCondition", "varchar(32)");
        FIELD_NAME_TO_MYSQL_TYPE.put("htmlType", "varchar(32)");
        FIELD_NAME_TO_MYSQL_TYPE.put("resultMsg", "varchar(512)");
    }

    public DefaultDDLGenerator() {
        this(new DDLGeneratorConfig());
    }

    public DefaultDDLGenerator(DDLGeneratorConfig config) {
        this.config = config;
    }

    @Override
    public String generateDDL(DataSource dataSource, Class<?> entityClass, String tag,boolean tenantEnable) {
        // 1. 获取表名
        String tableName = resolveTableName(entityClass);

        // 2. 获取表注释
        String tableComment = resolveTableComment(entityClass);

        // 3. 获取所有字段定义
        List<ColumnDefinition> columns = resolveColumnDefinitions(entityClass);

        // 3.1 根据配置是否增强租户
        if (tenantEnable) {
            boolean tenantId = columns.stream().anyMatch(e -> e.getColumnName().equalsIgnoreCase("tenant_id"));
            if (!tenantId) {
                columns.add(ColumnDefinition.tenantColumnDefinition());
            }
        }
        // 4. 识别主键
        List<ColumnDefinition> primaryKeys = columns.stream()
                .filter(ColumnDefinition::isPrimaryKey)
                .collect(Collectors.toList());
        // 4.1 增强table的注释
        String suffix = StrUtil.isNotEmpty(tag) ? " create_by:" + tag : "";
        tableComment = tableComment + suffix;

        // 5. 构建DDL语句
        return buildDDL(tableName, tableComment, columns, primaryKeys);
    }

    private String resolveTableName(Class<?> entityClass) {
        TableName tableNameAnnotation = entityClass.getAnnotation(TableName.class);
        if (tableNameAnnotation != null) {
            return tableNameAnnotation.value();
        }
        return StringUtils.camelToUnderline(entityClass.getSimpleName());
    }

    private String resolveTableComment(Class<?> entityClass) {
        String comment = "";

        // 1. 尝试从注解获取
        if (entityClass.isAnnotationPresent(TableComment.class)) {
            comment = entityClass.getAnnotation(TableComment.class).value();
        }

        // 2. 尝试从Javadoc获取
        if (comment.isEmpty()) {
            comment = extractJavadocComment(entityClass);
        }

        // 3. 使用类名作为备选
        if (comment.isEmpty()) {
            comment = entityClass.getSimpleName()
                    .replace("DO", "")
                    .replace("Entity", "") + " Table";
        }

        return sanitizeComment(comment);
    }

    private List<ColumnDefinition> resolveColumnDefinitions(Class<?> entityClass) {
        List<ColumnDefinition> columns = new ArrayList<>();
        Set<String> processedFields = new HashSet<>();

        // 递归处理类及其父类的字段
        Class<?> currentClass = entityClass;
        while (currentClass != null && currentClass != Object.class) {
            for (Field field : currentClass.getDeclaredFields()) {
                if (Modifier.isStatic(field.getModifiers())) {
                    continue; // 跳过静态字段
                }

                String fieldName = field.getName();
                if (processedFields.contains(fieldName)) {
                    continue; // 避免重复处理重名字段
                }
                processedFields.add(fieldName);

                columns.add(createColumnDefinition(field));
            }
            currentClass = currentClass.getSuperclass();
        }
        return columns;
    }

    private ColumnDefinition createColumnDefinition(Field field) {
        ColumnDefinition column = new ColumnDefinition();

        // 字段名
        column.setColumnName(resolveColumnName(field));

        // 字段类型
        column.setDataType(resolveDataType(field));

        // 是否主键
        column.setPrimaryKey(field.isAnnotationPresent(TableId.class) || field.getName().equalsIgnoreCase("id"));

        // 是否自增
        column.setAutoIncrement(column.isPrimaryKey() &&
                (field.getType() == Long.class || field.getType() == long.class));

        // 是否允许为空
        column.setNullable(resolveNullable(field));

        if (!column.isPrimaryKey()) {
            // 默认值
            column.setDefaultValue(resolveDefaultValue(field));
        }


        // 字段注释
        column.setComment(resolveColumnComment(field));

        return column;
    }

    private String resolveColumnName(Field field) {
        TableField tableFieldAnnotation = field.getAnnotation(TableField.class);
        if (tableFieldAnnotation != null && !tableFieldAnnotation.value().isEmpty()) {
            return tableFieldAnnotation.value();
        }
        return StringUtils.camelToUnderline(field.getName());
    }

    private String resolveDataType(Field field) {
        String fieldName = field.getName();

        // 1. 检查特殊字段映射
        if (FIELD_NAME_TO_MYSQL_TYPE.containsKey(fieldName)) {
            return FIELD_NAME_TO_MYSQL_TYPE.get(fieldName);
        }

        // 2. 检查注解指定的类型
        ColumnType columnTypeAnnotation = field.getAnnotation(ColumnType.class);
        if (columnTypeAnnotation != null) {
            return columnTypeAnnotation.value();
        }

        // 3. 使用类型映射
        Class<?> fieldType = field.getType();
        String baseType = JAVA_TO_MYSQL_TYPE.getOrDefault(fieldType, "varchar(255)");

        // 4. 特殊处理枚举类型
        if (fieldType.isEnum()) {
            return config.getEnumMappingStrategy().apply(fieldType);
        }

        return baseType;
    }

    private boolean resolveNullable(Field field) {
        // 主键字段不允许为空
        if (field.isAnnotationPresent(TableId.class)) {
            return false;
        }

        // 基本类型不允许为空
        if (field.getType().isPrimitive()) {
            return false;
        }

        // 检查@TableField注解
        TableField tableFieldAnnotation = field.getAnnotation(TableField.class);
        if (tableFieldAnnotation != null) {
            return tableFieldAnnotation.exist();
        }

        // 默认允许为空
        return true;
    }

    private String resolveDefaultValue(Field field) {
        // 特殊字段的默认值
        String fieldName = field.getName();
        if ("deleted".equals(fieldName)) {
            return "b'0'";
        }
        if ("tenantId".equals(fieldName)) {
            return "0";
        }

        // 检查@ColumnDefault注解
        ColumnDefault columnDefaultAnnotation = field.getAnnotation(ColumnDefault.class);
        if (columnDefaultAnnotation != null) {
            return columnDefaultAnnotation.value();
        }

        // 时间字段的特殊处理
        if (field.getType() == LocalDateTime.class) {
            if ("createTime".equals(fieldName)) {
                return "CURRENT_TIMESTAMP";
            }
            if ("updateTime".equals(fieldName)) {
                return "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP";
            }
        }

        // 使用类型默认值
        return JAVA_TO_MYSQL_DEFAULT.getOrDefault(field.getType(), null);
    }

    private String resolveColumnComment(Field field) {
        String comment = "";

        // 1. 尝试从注解获取
        if (field.isAnnotationPresent(ColumnComment.class)) {
            comment = field.getAnnotation(ColumnComment.class).value();
        }

        // 2. 尝试从Javadoc获取
        if (comment.isEmpty()) {
            comment = extractJavadocComment(field);
        }

        // 3. 使用字段名作为备选
        if (comment.isEmpty()) {
            comment = field.getName().replaceAll("([A-Z])", " $1");
        }

        return sanitizeComment(comment);
    }

    private String extractJavadocComment(Class<?> clazz) {
        return JavadocExtractor.extractClassJavadoc(clazz);
    }

    private String extractJavadocComment(Field field) {
        return JavadocExtractor.extractFieldJavadoc(field.getDeclaringClass(), field.getName());
    }

    private String sanitizeComment(String comment) {
        if (comment == null || comment.isEmpty()) {
            return "";
        }
        // 移除注释中的特殊字符
        return comment.replace("'", "''")
                .replace("\"", "\\\"")
                .replace("\n", " ")
                .replace("\r", "")
                .trim();
    }

    private String buildDDL(String tableName, String tableComment,
                            List<ColumnDefinition> columns,
                            List<ColumnDefinition> primaryKeys) {
        StringBuilder ddl = new StringBuilder();

        // 1. 删除已存在的表
        ddl.append("\n\nDROP TABLE IF EXISTS `").append(tableName).append("`;\n");

        // 2. 创建表
        ddl.append("CREATE TABLE `").append(tableName).append("` (\n");

        // 3. 添加列定义
        for (int i = 0; i < columns.size(); i++) {
            ColumnDefinition column = columns.get(i);
            ddl.append("  `").append(column.getColumnName()).append("` ")
                    .append(column.getDataType());

            if (!column.isNullable()) {
                ddl.append(" NOT NULL");
            }

            if (column.getDefaultValue() != null) {
                ddl.append(" DEFAULT ").append(column.getDefaultValue());
            }

            if (column.isAutoIncrement()) {
                ddl.append(" AUTO_INCREMENT");
            }

            if (!column.getComment().isEmpty()) {
                ddl.append(" COMMENT '").append(column.getComment()).append("'");
            }

            if (i < columns.size() - 1 || !primaryKeys.isEmpty()) {
                ddl.append(",");
            }

            ddl.append("\n");
        }

        // 4. 添加主键
        if (!primaryKeys.isEmpty()) {
            ddl.append("  PRIMARY KEY (");
            ddl.append(primaryKeys.stream()
                    .map(col -> "`" + col.getColumnName() + "`")
                    .collect(Collectors.joining(", ")));
            ddl.append(") USING BTREE");

            // 检查是否需要添加索引
            boolean hasCreateTime = columns.stream()
                    .anyMatch(col -> "create_time".equals(col.getColumnName()));

            if (hasCreateTime) {
                ddl.append(",\n");
            } else {
                ddl.append("\n");
            }
        }

        // 5. 添加索引（特殊处理create_time）
        if (columns.stream().anyMatch(col -> "create_time".equals(col.getColumnName()))) {
            ddl.append("  INDEX `idx_create_time`(`create_time` ASC) USING BTREE\n");
        }

        // 6. 表选项
        ddl.append(") ENGINE = ").append(config.getEngine()).append(" ")
                .append("CHARACTER SET = ").append(config.getCharset()).append(" ")
                .append("COLLATE = ").append(config.getCollation());

        if (!tableComment.isEmpty()) {
            ddl.append(" COMMENT '").append(tableComment).append("'");
        }

        ddl.append(";");

        return ddl.toString();
    }

    // 辅助类：列定义
    private static class ColumnDefinition {
        private String columnName;
        private String dataType;
        private boolean primaryKey;
        private boolean autoIncrement;
        private boolean nullable;
        private String defaultValue;
        private String comment;

        public static ColumnDefinition tenantColumnDefinition() {
            ColumnDefinition columnDefinition = new ColumnDefinition();
            columnDefinition.setColumnName("tenant_id");
            columnDefinition.setDataType("bigint");
            columnDefinition.setPrimaryKey(false);
            columnDefinition.setAutoIncrement(false);
            columnDefinition.setNullable(false);
            columnDefinition.setDefaultValue("0");
            columnDefinition.setComment("租户编号");
            return columnDefinition;
        }

        // getters and setters
        public String getColumnName() {
            return columnName;
        }

        public void setColumnName(String columnName) {
            this.columnName = columnName;
        }

        public String getDataType() {
            return dataType;
        }

        public void setDataType(String dataType) {
            this.dataType = dataType;
        }

        public boolean isPrimaryKey() {
            return primaryKey;
        }

        public void setPrimaryKey(boolean primaryKey) {
            this.primaryKey = primaryKey;
        }

        public boolean isAutoIncrement() {
            return autoIncrement;
        }

        public void setAutoIncrement(boolean autoIncrement) {
            this.autoIncrement = autoIncrement;
        }

        public boolean isNullable() {
            return nullable;
        }

        public void setNullable(boolean nullable) {
            this.nullable = nullable;
        }

        public String getDefaultValue() {
            return defaultValue;
        }

        public void setDefaultValue(String defaultValue) {
            this.defaultValue = defaultValue;
        }

        public String getComment() {
            return comment;
        }

        public void setComment(String comment) {
            this.comment = comment;
        }
    }
}